﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Xml;
using System.Xml.XPath;
using System.Xml.Xsl;

namespace InfoPathHelpers.RepeatingTable
{
    /// <summary>
    /// Used for sorting repeating tables.
    /// </summary>
    public class RepeatingTableSorter
    {
        private XPathNavigator _Table;
        private string _CurrentColumn;
        private XmlSortOrder _CurrentSortOrder;
        private XslCompiledTransform _Transform;
        private OrderedDictionary _Columns;

        /// <summary>
        /// Creates an instance of the RepeatingTableSorter, used to sort repeating tables.
        /// </summary>
        /// <param name="table">An XPathNavigator positioned on the table you wish to sort.</param>
        public RepeatingTableSorter(XPathNavigator table)
        {
            if (table == null)
            {
                throw new ArgumentNullException("table");
            }

            _Table = table;
            _Columns = new OrderedDictionary();
            _CurrentSortOrder = XmlSortOrder.Ascending;
            LoadTransform();
        }

        /// <summary>
        /// Creates an instance of the RepeatingTableSorter, used to sort repeating tables.
        /// </summary>
        /// <param name="formContext"></param>
        /// <param name="xPath"></param>
        public RepeatingTableSorter(FormContext formContext, string xPath) : this(formContext.SelectSingleNode(xPath)) { }

        /// <summary>
        /// Gets or sets the current sort order.
        /// </summary>
        public XmlSortOrder SortOrder
        {
            get { return _CurrentSortOrder; }
            set { _CurrentSortOrder = value; }
        }

        /// <summary>
        /// Gets or sets the name of the current column being sorted.
        /// </summary>
        /// <exception cref="InvalidOperationException">Thrown if the supplied value is not a defined column.</exception>
        public string CurrentColumn
        {
            get { return _CurrentColumn; }
            set 
            {
                if (!_Columns.Contains(value))
                {
                    throw new InvalidOperationException("The CurrentColumn can only be set to a defined column.");
                }
                _CurrentColumn = value; 
            }
        }

        public void AddColumn(string columnName, XmlDataType dataType)
        {
            if (_Columns.Contains(columnName))
            {
                throw new ArgumentException("Column has already been defined", "columnName");
            }

            _Columns.Add(columnName, dataType);
        }

        /// <summary>
        /// Reverses the current sort direction (does not perform sort).
        /// </summary>
        public void ReverseSortOrder()
        {
            _CurrentSortOrder = _CurrentSortOrder == XmlSortOrder.Ascending ?
                XmlSortOrder.Descending : XmlSortOrder.Ascending;
        }

        /// <summary>
        /// Sorts using the current sort column (effectively reverses the sort direction).
        /// </summary>
        public void Sort()
        {
            Sort(_CurrentColumn);
        }

        /// <summary>
        /// Sorts the table by the <paramref name="columnName"/>
        /// </summary>
        /// <param name="columnName">The name of the column to sort on.</param>
        public void Sort(string columnName)
        {
            if (!_Columns.Contains(columnName))
            {
                throw new ArgumentException("Column has not been defined", "columnName");
            }

            if (_CurrentColumn == columnName)
            {
                ReverseSortOrder();
            }
            else
            {
                _CurrentColumn = columnName;
                _CurrentSortOrder = XmlSortOrder.Ascending;
            }

            XsltArgumentList XslArguments = new XsltArgumentList();
            XslArguments.AddParam("sortField", string.Empty, _CurrentColumn);
            XslArguments.AddParam("sortOrder", string.Empty, _CurrentSortOrder.ToString().ToLowerInvariant());
            XslArguments.AddParam("sortType", string.Empty, _Columns[columnName].ToString().ToLowerInvariant());

            Sort(XslArguments);
        }

        /// <summary>
        /// Sorts the table by the column at <paramref name="columnIndex"/>
        /// </summary>
        /// <param name="columnIndex">The 0 based index of the column to sort.</param>
        public void Sort(int columnIndex)
        {
            if (_Columns.Count < columnIndex)
            {
                throw new ArgumentException("The column index is out of range", "columnNumber");
            }
            object[] Keys = new object[_Columns.Count];
            _Columns.Keys.CopyTo(Keys, 0);
            string ColumnName = Keys[columnIndex].ToString();

            Sort(ColumnName);
        }

        private void Sort(XsltArgumentList arguments)
        {
            using (var Output = new MemoryStream())
            {
                // Read table xml.
                using (var StringReader = new StringReader(_Table.OuterXml))
                using (var Input = XmlReader.Create(StringReader))
                {
                    // And transform into output stream using supplied arguments.
                    _Transform.Transform(Input, arguments, Output);
                }

                // Return memory stream to beginning so it can be read from.
                Output.Position = 0;

                // Use an XmlReader to read the xml from the stream.
                using (XmlReader Reader = XmlReader.Create(Output))
                {
                    // Replace the table with the contents of the reader.
                    // Note: If you don't use a reader here it does not write formatted xml.
                    _Table.ReplaceSelf(Reader);
                }
            }
        }

        private void LoadTransform()
        {
            _Transform = new XslCompiledTransform();
            using (StringReader Input = new StringReader (Resources.Resources.SortTable))
            using (XmlReader StyleSheetReader = XmlReader.Create(Input))
            {
                var StyleSheet = new XPathDocument(StyleSheetReader).CreateNavigator();
                _Transform.Load(StyleSheet);
            }
        }
    }
}
